home *** CD-ROM | disk | FTP | other *** search
/ IRIX 6.4 Applications 1997 August / SGI IRIX 6.4 Applications 1997 August.iso / dist / sitemgr.idb / usr / sitemgr / bin / weblint.z / weblint
Encoding:
Text File  |  1997-07-30  |  49.2 KB  |  1,750 lines

  1. : # use perl                                 -*- mode: Perl; -*-
  2.     eval 'exec perl -S $0 "$@"'
  3.         if $runnning_under_some_shell;
  4.  
  5. #
  6. # weblint - pick fluff off WWW pages (html).
  7. #
  8. # Copyright (C) 1994, 1995, 1996 Neil Bowers.  All rights reserved.
  9. #
  10. # See README for additional blurb.
  11. # Bugs, comments, suggestions welcome: neilb@cre.canon.co.uk
  12. #
  13. # Latest version is available as:
  14. #    ftp://ftp.cre.canon.co.uk/pub/weblint/weblint.tar.gz
  15. #
  16.  
  17. $VERSION        = '1.017';
  18. ($PROGRAM       = $0) =~ s@.*/@@;
  19. @TMPDIR_OPTIONS    = ('/usr/tmp', '/tmp', '/var/tmp', '/temp');
  20. $TMPDIR         = &PickTmpdir(@TMPDIR_OPTIONS);
  21. $SITE_DIR       = '';
  22. $USER_RCFILE    = $ENV{'WEBLINTRC'} || "$ENV{'HOME'}/.weblintrc";
  23. $SITE_RCFILE    = $SITE_DIR.'/global.weblintrc' if $SITE_DIR;
  24.  
  25.  
  26. #------------------------------------------------------------------------
  27. # $version - the string which is displayed with -v or -version
  28. #------------------------------------------------------------------------
  29. $versionString=<<EofVersion;
  30.     This is weblint, version $VERSION
  31.  
  32.     Copyright 1994,1995,1996 Neil Bowers
  33.  
  34.     Weblint may be used and copied only under the terms of the Artistic
  35.     License, which may be found in the Weblint source kit, or at:
  36.             http://www.cre.canon.co.uk/~neilb/weblint/artistic.html
  37. EofVersion
  38.  
  39.  
  40. #------------------------------------------------------------------------
  41. # $usage - usage string displayed with the -U command-line switch
  42. #------------------------------------------------------------------------
  43. $usage=<<EofUsage;
  44.   $PROGRAM v$VERSION - pick fluff off web pages (HTML)
  45.       -d            : disable specified warnings (warnings separated by commas)
  46.       -e            : enable specified warnings (warnings separated by commas)
  47.       -f filename   : alternate configuration file
  48.       -stderr       : print warnings to STDERR rather than STDOUT
  49.       -i            : ignore case in element tags
  50.       -l            : ignore symlinks when recursing in a directory
  51.       -pedantic     : turn on all warnings, except for case of element tags
  52.       -s            : give short warning messages (filename not printed)
  53.       -t            : terse warning mode, useful mainly for testsuite
  54.       -todo         : print the todo list for $PROGRAM
  55.       -help | -U    : display this usage message
  56.       -urlget       : specify the command used to get a URL
  57.       -version | -v : display version
  58.       -warnings     : list supported warnings
  59.       -x <extn>     : HTML extension to include (supported: Java, Netscape)
  60.  
  61.   To check one or more HTML files, run weblint thusly:
  62.       weblint file1.html [... fileN.html]
  63.   If a file is in fact a directory, weblint will recurse, checking all files.
  64.  
  65.   To include the Netscape extensions: weblint -x Netscape file.html
  66. EofUsage
  67.  
  68. #------------------------------------------------------------------------
  69. # $todo - string displayed with the -todo switch
  70. #------------------------------------------------------------------------
  71. $todo=<<EofToDo;
  72. The Weblint toDo list can be seen at:
  73.     http://www.cre.canon.co.uk/~neilb/weblint/todo/
  74. EofToDo
  75.  
  76. *WARNING = *STDOUT;
  77.  
  78. # obsolete tags
  79. $obsoleteTags = 'PLAINTEXT|XMP|LISTING|COMMENT';
  80.  
  81. $maybePaired  = 'LI|DT|DD|P|ROW|TD|TH|TR';
  82.  
  83. $pairElements = 'A|ABBREV|ABOVE|ACRONYM|ADDRESS|ARRAY|AU|'.
  84.                 'HTML|HEAD|BANNER|BAR|BELOW|BIG|BLOCKQUOTE|BODY|BOX|BQ|BT|'.
  85.                 'CAPTION|CREDIT|DDOT|DEL|DIV|DOT|'.
  86.                 'FIG|FN|H1|H2|H3|H4|H5|H6|HAT|INS|LH|OVERLAY|'.
  87.         'B|I|U|TT|STRONG|EM|CODE|KBD|VAR|DFN|CITE|SAMP|Q|LANG|'.
  88.         'UL|OL|DL|'.
  89.                 'MATH|MENU|DIR|FORM|NOTE|PERSON|ROOT|'.
  90.                 'S|SELECT|SMALL|SQRT|STRIKE|STYLE|'.
  91.                 'SUB|SUP|T|TABLE|TEXT|TEXTAREA|TILDE|TITLE|VEC|CODE|PRE|'.
  92.                 $maybePaired.'|'.
  93.                 $obsoleteTags;
  94.  
  95. # container elements which shouldn't have leading or trailing whitespace
  96. $cuddleContainers = 'A|H1|H2|H3|H4|H5|H6|TITLE|LI';
  97.  
  98. # expect to see these tags only once
  99. %onceOnly = ('HTML', 1, 'HEAD', 1, 'BODY', 1, 'TITLE', 1);
  100.  
  101. @fontElements = ('EM', 'CITE', 'STRONG', 'CODE', 'SAMP', 'KBD', 'VAR',
  102.          'DFN', 'Q', 'LANG', 'AU', 'PERSON', 'ACRONYM', 'ABBREV',
  103.          'INS', 'DEL',
  104.          'B', 'I', 'TT', 'U', 'S', 'BIG', 'SMALL', 'SUB', 'SUP');
  105.  
  106. %physicalFontElements =
  107. (
  108.  'B',  'STRONG',
  109.  'I',  'EM',
  110.  'TT', 'CODE, SAMP, KBD, or VAR'
  111.  );
  112.  
  113. # expect these tags to have attributes
  114. # these are elements which have no required attributes, but we expect to
  115. # see at least one of the attributes
  116. $expectArgsRE = 'A';
  117.  
  118. # these tags can only appear in the head element
  119. $headTagsRE = 'TITLE|NEXTID|LINK|BASE|META';
  120.  
  121. %requiredContext =
  122. (
  123.  'ABOVE',     'MATH',
  124.  'ARRAY',     'MATH',
  125.  'ATOP',      'BOX',
  126.  'BAR',       'MATH',
  127.  'BELOW',     'MATH',
  128.  'BOX',       'MATH',
  129.  'BT',        'MATH',
  130.  'CAPTION',   'TABLE|FIG',
  131.  'CHOOSE',    'BOX',
  132.  'DD',        'DL',
  133.  'DDOT',      'MATH',
  134.  'DOT',       'MATH',
  135.  'DT',        'DL',
  136.  'HAT',       'MATH',
  137.  'INPUT',     'FORM',
  138.  'ITEM',      'ROW',
  139.  'LEFT',      'BOX',
  140.  'LH',        'DL|OL|UL',
  141.  'LI',        'DIR|MENU|OL|UL',
  142.  'OF',        'ROOT',
  143.  'OPTION',    'SELECT',
  144.  'OVER',      'BOX',
  145.  'OVERLAY',   'FIG',
  146.  'RIGHT',     'BOX',
  147.  'ROOT',      'MATH',
  148.  'ROW',       'ARRAY',
  149.  'SELECT',    'FORM',
  150.  'SQRT',      'MATH',
  151.  'T',         'MATH',
  152.  'TD',        'TR',
  153.  'TEXT',      'MATH',
  154.  'TEXTAREA',  'FORM',
  155.  'TH',        'TR',
  156.  'TILDE',     'MATH',
  157.  'TR',        'TABLE',
  158.  'VEC',       'MATH'
  159.  );
  160.  
  161. # these tags are allowed to appear in the head element
  162. %okInHead = ('ISINDEX', 1, 'TITLE', 1, 'NEXTID', 1, 'LINK', 1,
  163.          'BASE', 1, 'META', 1, 'RANGE', 1, 'STYLE', 1, '!--', 1);
  164.  
  165. # expect to see these at least once.
  166. # html-outer covers the HTML element
  167. @expectedTags = ('HEAD', 'TITLE', 'BODY');
  168.  
  169. # elements which cannot be nested
  170. $nonNest = 'A|FORM';
  171.  
  172. $netscapeElements = 'NOBR|WBR|FONT|FRAME|FRAMESET|NOFRAMES|BASEFONT|BLINK|'.
  173.                     'CENTER|MAP|AREA|SCRIPT';
  174. $javaElements = 'APPLET|PARAM';
  175.  
  176. #
  177. # This is a regular expression for all legal elements
  178. # UPDATE: need to remove duplication in legalElements and pairElements
  179. #
  180. $legalElements =
  181.    'A|ABBREV|ABOVE|ACRONYM|ADDRESS|ARRAY|ATOP|AU|'.
  182.    'B|BANNER|BAR|BASE|BELOW|BIG|BLOCKQUOTE|BODY|BOX|BQ|BR|BT|'.
  183.    'CAPTION|CHOOSE|CITE|CODE|CREDIT|'.
  184.    'DD|DDOT|DFN|DEL|DIR|DIV|DL|DOT|DT|'.
  185.    'EM|FIG|FN|FORM|H1|H2|H3|H4|H5|H6|HAT|HEAD|HR|HTML|'.
  186.    'I|IMG|INPUT|INS|ISINDEX|ITEM|KBD|'.
  187.    'LANG|LEFT|LH|LI|LINK|MATH|MENU|META|NEXTID|NOTE|'.
  188.    'OF|OL|OPTION|OVER|OVERLAY|P|PERSON|PRE|Q|RANGE|RIGHT|ROOT|ROW|'.
  189.    'SAMP|SELECT|S|SMALL|SQRT|STRIKE|STRONG|STYLE|SUB|SUP|'.
  190.    'T|TAB|TABLE|TD|TEXT|TEXTAREA|TH|TILDE|TITLE|TR|TT|U|UL|VAR|VEC|'.
  191.    $obsoleteTags;
  192.  
  193. # This table holds the valid attributes for elements
  194. # Where an element does not have an entry, this implies that the element
  195. # does not take any attributes
  196. %validAttributes =
  197.    (
  198.    'A',          'ID|LANG|CLASS|HREF|MD|NAME|SHAPE|TITLE|REL|REV',
  199.    'ABOVE',      'SYM',
  200.    'ADDRESS',    'ID|LANG|CLASS|CLEAR|NOWRAP',
  201.    'ARRAY',      'ALIGN|COLDEF|LDELIM|RDELIM|LABELS',
  202.    'BANNER',     'ID|LANG|CLASS',
  203.    'BASE',       'HREF',
  204.    'BR',         'ID|LANG|CLASS|CLEAR',
  205.    'BLOCKQUOTE', 'ID|LANG|CLASS|CLEAR|NOWRAP',
  206.    'BODY',       'ID|LANG|CLASS|BACKGROUND',
  207.    'BOX',        'SIZE',
  208.    'BQ',         'ID|LANG|CLASS|CLEAR|NOWRAP',
  209.    'BELOW',      'SYM',
  210.    'CAPTION',    'ID|LANG|CLASS|ALIGN',
  211.    'CREDIT',     'ID|LANG|CLASS',
  212.    'DD',         'ID|LANG|CLASS|CLEAR',
  213.    'DIV',        'ID|LANG|CLASS|ALIGN|NOWRAP|CLEAR',
  214.    'DL',         'ID|LANG|CLASS|CLEAR|COMPACT',
  215.    'DT',         'ID|LANG|CLASS|CLEAR',
  216.    'FIG',        'ID|LANG|CLASS|CLEAR|NOFLOW|SRC|MD|ALIGN|WIDTH|HEIGHT|'.
  217.                  'UNITS|IMAGEMAP',
  218.    'FN',         'ID|LANG|CLASS',
  219.    'FORM',       'ACTION|METHOD|ENCTYPE|SCRIPT',
  220.    'H1',         'ID|LANG|CLASS|ALIGN|CLEAR|SEQNUM|SKIP|DINGBAT|SRC|MD|NOWRAP',
  221.    'H2',         'ID|LANG|CLASS|ALIGN|CLEAR|SEQNUM|SKIP|DINGBAT|SRC|MD|NOWRAP',
  222.    'H3',         'ID|LANG|CLASS|ALIGN|CLEAR|SEQNUM|SKIP|DINGBAT|SRC|MD|NOWRAP',
  223.    'H4',         'ID|LANG|CLASS|ALIGN|CLEAR|SEQNUM|SKIP|DINGBAT|SRC|MD|NOWRAP',
  224.    'H5',         'ID|LANG|CLASS|ALIGN|CLEAR|SEQNUM|SKIP|DINGBAT|SRC|MD|NOWRAP',
  225.    'H6',         'ID|LANG|CLASS|ALIGN|CLEAR|SEQNUM|SKIP|DINGBAT|SRC|MD|NOWRAP',
  226.    'HR',         'ID|CLASS|CLEAR|SRC|MD',
  227.    'HTML',       'VERSION|URN|ROLE',
  228.    'IMG',        'ID|LANG|CLASS|SRC|MD|WIDTH|HEIGHT|UNITS|ALIGN|ALT|ISMAP',
  229.    'INPUT',      'ID|LANG|CLASS|TYPE|NAME|VALUE|DISABLED|ERROR|CHECKED|SIZE|'.
  230.                  'MAXLENGTH|MIN|MAX|ACCEPT|SRC|MD|ALIGN',
  231.    'ITEM',       'ALIGN|COLSPAN|ROWSPAN',
  232.    'LH',         'ID|LANG|CLASS',
  233.    'LI',         'ID|LANG|CLASS|CLEAR|SRC|MD|DINGBAT|SKIP',
  234.    'LINK',       'HREF|REL|REV|URN|TITLE|METHODS',
  235.    'MATH',       'ID|CLASS|BOX',
  236.    'META',       'HTTP-EQUIV|NAME|CONTENT',
  237.    'NEXTID',     'N',
  238.    'NOTE',       'ID|LANG|CLASS|CLEAR|SRC|MD',
  239.    'OL',         'ID|LANG|CLASS|CLEAR|CONTINUE|SEQNUM|COMPACT',
  240.    'OPTION',     'ID|LANG|CLASS|DISABLED|ERROR|VALUE|SELECTED|SHAPE',
  241.    'OVERLAY',    'SRC|MD|UNITS|X|Y|WIDTH|HEIGHT',
  242.    'P',          'ID|LANG|CLASS|ALIGN|CLEAR|NOWRAP',
  243.    'PRE',        'ID|LANG|CLASS|CLEAR|WIDTH',
  244.    'RANGE',      'ID|CLASS|FROM|UNTIL',
  245.    'ROW',        'ALIGN|COLSPAN|ROWSPAN',
  246.    'SELECT',     'ID|LANG|CLASS|NAME|MULTIPLE|DISABLED|ERROR|SRC|MD|WIDTH|'.
  247.                  'HEIGHT|UNITS|ALIGN|SIZE',
  248.    'STYLE',      'NOTATION',
  249.    'TAB',        'ID|INDENT|TO|ALIGN|DP',
  250.    'TABLE',      'ID|LANG|CLASS|CLEAR|NOFLOW|ALIGN|UNITS|COLSPEC|DP|WIDTH|'.
  251.                  'BORDER|NOWRAP',
  252.    'TD',         'ID|LANG|CLASS|COLSPAN|ROWSPAN|ALIGN|DP|VALIGN|NOWRAP|'.
  253.                  'AXIS|AXES',
  254.    'TEXTAREA',   'ID|LANG|CLASS|NAME|ROWS|COLS|DISABLED|ERROR|ALIGN',
  255.    'TH',         'ID|LANG|CLASS|COLSPAN|ROWSPAN|ALIGN|DP|VALIGN|NOWRAP|'.
  256.                  'AXIS|AXES',
  257.    'TR',         'ID|LANG|CLASS|ALIGN|DP|VALIGN|NOWRAP',
  258.    'UL',         'ID|LANG|CLASS|CLEAR|PLAIN|SRC|MD|DINGBAT|WRAP|COMPACT',
  259.    );
  260.  
  261. foreach $elt (@fontElements)
  262. {
  263.    $validAttributes{$elt} = 'ID|LANG|CLASS';
  264. }
  265.  
  266. %requiredAttributes =
  267.    (
  268.    'BASE',     'HREF',
  269.    'FORM',     'ACTION',
  270.    'IMG',      'SRC',
  271.    'LINK',     'HREF',
  272.    'NEXTID',   'N',
  273.    'SELECT',   'NAME',
  274.    'STYLE',    'NOTATION',
  275.    'TEXTAREA', 'NAME|ROWS|COLS'
  276.    );
  277.  
  278. $colorRE = '#[0-9a-fA-F]{6}';
  279. %attributeFormat =
  280. (
  281.  'ALIGN',     'BOTTOM|MIDDLE|TOP|LEFT|CENTER|RIGHT|JUSTIFY|'.
  282.               'BLEEDLEFT|BLEEDRIGHT|DECIMAL',
  283.  'BGCOLOR',   $colorRE,
  284.  'CLEAR',     'LEFT|RIGHT|ALL',
  285.  'COLS',      '\d+',
  286.  'COLSPAN',   '\d+',
  287.  'HEIGHT',    '\d+',
  288.  'INDENT',    '\d+',
  289.  'MAXLENGTH', '\d+',
  290.  'METHOD',    'GET|POST',
  291.  'ROWS',      '\d+',
  292.  'ROWSPAN',   '\d+',
  293.  'SEQNUM',    '\d+',
  294.  'SIZE',      '\d+|\d+,\d+',
  295.  'SKIP',      '\d+',
  296.  'TYPE',      'CHECKBOX|HIDDEN|IMAGE|PASSWORD|RADIO|RESET|SUBMIT|TEXT|',
  297.  'UNITS',     'PIXELS|EN',
  298.  'VALIGN',    'TOP|MIDDLE|BOTTOM|BASELINE',
  299.  'WIDTH',     '\d+',
  300.  'WRAP',      'OFF|VIRTUAL|PHYSICAL',
  301.  'X',         '\d+',
  302.  'Y',         '\d+'
  303. );
  304.  
  305. %netscapeAttributes =
  306. (
  307.  'A',        'TARGET',
  308.  'AREA',     'SHAPE|HREF|COORDS|NOHREF|TARGET',
  309.  'BASE',     'TARGET',
  310.  'BASEFONT', 'SIZE',
  311.  'BODY',     'BGCOLOR|TEXT|LINK|VLINK|ALINK',
  312.  'FONT',     'COLOR|SIZE',
  313.  'FORM',     'ENCTYPE|TARGET',
  314.  'FRAME',    'SRC|NAME|MARGINWIDTH|MARGINHEIGHT|SCROLLING|NORESIZE',
  315.  'FRAMESET', 'ROWS|COLS',
  316.  'HR',       'SIZE|WIDTH|ALIGN|NOSHADE',
  317.  'IMG',      'BORDER|VSPACE|HSPACE|LOWSRC|USEMAP',
  318.  'ISINDEX',  'PROMPT',
  319.  'LI',       'TYPE|VALUE',
  320.  'MAP',      'NAME',
  321.  'OL',       'TYPE|START',
  322.  'SCRIPT',   'LANGUAGE',
  323.  'TABLE',    'CELLSPACING|CELLPADDING',
  324.  'TEXTAREA', 'WRAP',
  325.  'TD',       'WIDTH|HEIGHT',
  326.  'TH',       'WIDTH|HEIGHT',
  327.  'TR',       'HEIGHT',
  328.  'UL',       'TYPE'
  329. );
  330.  
  331. $msElements = 'AREA|BASEFONT|BGSOUND|CENTER|FONT|MAP|MARQUEE|NOBR|WBR';
  332. %msAttributes =
  333. (
  334.  'AREA',     'SHAPE|HREF|COORDS|NOHREF',
  335.  'BASEFONT', 'SIZE',
  336.  'BGSOUND',  'SRC|LOOP',
  337.  'BODY',     'ALINK|BGCOLOR|BGPROPERTIES|LEFTMARGIN|LINK|TEXT|TOPMARGIN|VLINK',
  338.  'CAPTION',  'VALIGN',
  339.  'FONT',     'COLOR|FACE|SIZE',
  340.  'HR',       'ALIGN|NOSHADE|SIZE|WIDTH',
  341.  'IMG',      'BORDER|CONTROLS|DYNSRC|HSPACE|LOOP|START|USEMAP|VSPACE',
  342.  'ISINDEX',  'ACTION|PROMPT',
  343.  'LI',       'TYPE|VALUE',
  344.  'MAP',      'NAME',
  345.  'MARQUEE',  'ALIGN|BEHAVIOR|BGCOLOR|DIRECTION|HEIGHT|HSPACE|LOOP|'.
  346.              'SCROLLAMOUNT|SCROLLDELAY|VSPACE|WIDTH',
  347.  'OL',       'START|TYPE',
  348.  'TABLE',    'BGCOLOR|BORDERCOLOR|BORDERCOLORLIGHT|BORDERCOLORDARK|'.
  349.          'CELLSPACING|CELLPADDING|VALIGN',
  350.  'TD',       'BGCOLOR|BORDERCOLOR|BORDERCOLORLIGHT|BORDERCOLORDARK|VALIGN|'.
  351.          'HEIGHT',
  352.  'TH',       'BGCOLOR|BORDERCOLOR|BORDERCOLORLIGHT|BORDERCOLORDARK|VALIGN|'.
  353.          'HEIGHT',
  354.  'TR',       'BGCOLOR|BORDERCOLOR|BORDERCOLORLIGHT|BORDERCOLORDARK|VALIGN|'.
  355.          'HEIGHT',
  356. );
  357. $msColors = 'Black|White|Green|Maroon|Olive|Navy|Purple|Gray|'.
  358.             'Red|Yellow|Blue|Teal|Lime|Aqua|Fuchsia|Silver';
  359.  
  360. %mustFollow =
  361. (
  362.  'LH',       'UL|OL|DL',
  363.  'OVERLAY',  'FIG',
  364.  'HEAD',     'HTML',
  365.  'BODY',     '/HEAD',
  366.  'FRAMESET', '/HEAD|/FRAME|/FRAMESET|/NOFRAMES',
  367.  '/HTML',    '/BODY|/FRAMESET',
  368.  );
  369.  
  370. %badTextContext =
  371. (
  372.  'HEAD',  'BODY, or TITLE perhaps',
  373.  'UL',    'LI or LH',
  374.  'OL',    'LI or LH',
  375.  'DL',    'DT or DD',
  376.  'TABLE', 'TD or TH',
  377.  'TR',    'TD or TH'
  378. );
  379.  
  380. %variable =
  381. (
  382.  'directory-index',        'index.html',
  383.  'file-extensions',        'html, htm',
  384.  'url-get',            '',
  385.  'message-style',        'lint'
  386. );
  387.  
  388. @options = ('d=s', 'e=s', 'f=s', 'stderr', 'help', 'i', 'l', 's', 't',
  389.         'todo', 'U',
  390.         'noglobals', 'pedantic', 'urlget=s', 'v', 'version', 'warnings',
  391.         'x=s');
  392.  
  393. $exit_status = 0;
  394.  
  395. require 'newgetopt.pl';
  396. require 'find.pl';
  397.  
  398. die "$usage" unless @ARGV > 0;
  399.  
  400. # escape the `-' command-line switch (for stdin), so NGetOpt don't mess wi' it
  401. grep(s/^-$/\tstdin\t/, @ARGV);
  402.  
  403. &NGetOpt(@options) || die "use -U switch to display usage statement\n";
  404.  
  405. # put back the `-' command-line switch, if it was there
  406. grep(s/^\tstdin\t$/-/, @ARGV);
  407.  
  408. die "$versionString\n"    if $opt_v || $opt_version;
  409. die "$usage"        if $opt_u || $opt_help;
  410.  
  411. &ReadDefaults();
  412.  
  413. # Read configuration
  414. if ($opt_f)
  415. {
  416.    &ReadConfigFile($opt_f);
  417. }
  418. elsif (-f $USER_RCFILE)
  419. {
  420.    &ReadConfigFile($USER_RCFILE);
  421. }
  422. elsif (! $opt_noglobals && -f $SITE_RCFILE)
  423. {
  424.    &ReadConfigFile($SITE_RCFILE);
  425. }
  426.  
  427. # must do this after reading their config file to see a valid url-get
  428. &PrintToDo()        if $opt_todo;
  429.  
  430. # pedantic command-line switch turns on all warnings except case checking
  431. if ($opt_pedantic)
  432. {
  433.    foreach $warning (keys %enabled)
  434.    {
  435.       &enableWarning($warning, 1);
  436.    }
  437.    &enableWarning('lower-case', 0);
  438.    &enableWarning('upper-case', 0);
  439.    &enableWarning('bad-link', 0);
  440.    &enableWarning('require-doctype', 0);
  441. }
  442.  
  443. &AddExtension("\L$opt_x")             if $opt_x;
  444. $variable{'message-style'} = 'short'  if $opt_s;
  445. $variable{'message-style'} = 'terse'  if $opt_t;
  446. $variable{'url-get'} = $opt_urlget    if $opt_urlget;
  447. *WARNING = *STDERR                    if $opt_stderr;
  448. &ListWarnings()                      if $opt_warnings;
  449.  
  450. ($fileExtensions = $variable{'file-extensions'}) =~ s/,\s*/\|/g;
  451.  
  452. # WARNING file handle is default
  453. select(WARNING);
  454.  
  455. $opt_l = 1                 if $ignore{'SYMLINKS'};
  456.  
  457. # -d to disable warnings
  458. if ($opt_d)
  459. {
  460.    for (split(/,/,$opt_d))
  461.    {
  462.       &enableWarning($_, 0);
  463.    }
  464. }
  465.  
  466. # -e to enable warnings
  467. if ($opt_e)
  468. {
  469.    for (split(/,/,$opt_e))
  470.    {
  471.       &enableWarning($_, 1) || next;
  472.    }
  473. }
  474.  
  475. # -i option to ignore case in element tags
  476. if ($opt_i)
  477. {
  478.    $enabled{'lower-case'} = $enabled{'upper-case'} = 0;
  479. }
  480.  
  481. if (defined $variable{'directory-index'})
  482. {
  483.    @dirIndices = split(/\s*,\s*/, $variable{'directory-index'});
  484. }
  485.  
  486. $argc = int(@ARGV);
  487. while (@ARGV > 0)
  488. {
  489.    $arg = shift(@ARGV);
  490.  
  491.    &CheckURL($arg), next if $arg =~ m!^(http|gopher|ftp)://!;
  492.  
  493.    &find($arg), next if -d $arg;
  494.  
  495.    if ($opt_l && -l $arg && $argc == 1)
  496.    {
  497.       warn "$PROGRAM: $arg is a symlink, but I'll check it anyway\n";
  498.    }
  499.  
  500.    &WebLint($arg), next if (-f $arg && -r $arg) || $arg eq '-';
  501.  
  502.    print "$PROGRAM: could not read $arg: $!\n";
  503. }
  504.  
  505. exit $exit_status;
  506.  
  507. #========================================================================
  508. # Function:    WebLint
  509. # Purpose:    This is the high-level interface to the checker.  It takes
  510. #        a file and checks for fluff.
  511. #========================================================================
  512. sub WebLint
  513. {
  514.    local($filename,$relpath) = @_;
  515.    local(@tags) = ();
  516.    local($tagRE) = ('');
  517.    local(@taglines) = ();
  518.    local(@orphans) = ();
  519.    local(@orphanlines) = ();
  520.    local(%seenPage);
  521.    local(%seenTag);
  522.    local(%whined);
  523.    local(*PAGE);
  524.    local($line) = ('');
  525.    local($id, $ID);
  526.    local($tag, $tagNum);
  527.    local($closing);
  528.    local($tail);
  529.    local(%args);
  530.    local($arg);
  531.    local($rest);
  532.    local($lastNonTag);
  533.    local(@notSeen);
  534.    local($seenMailtoLink) = (0);
  535.    local($matched);
  536.    local($matchedLine);
  537.    local($novalue);
  538.    local($heading);
  539.    local($headingLine);
  540.    local($commentline);
  541.    local($_);
  542.  
  543.  
  544.    if ($filename eq '-')
  545.    {
  546.       *PAGE = *STDIN;
  547.       $filename = 'stdin';
  548.    }
  549.    else
  550.    {
  551.       return if defined $seenPage{$filename};
  552.       if (-d $filename)
  553.       {
  554.      print "$PROGRAM: $filename is a directory.\n";
  555.      $exit_status = 0;
  556.      return;
  557.       }
  558.       $seenPage{$filename}++;
  559.       open(PAGE,"<$filename") || do
  560.       {
  561.      print "$PROGRAM: could not read file $filename: $!\n";
  562.      $exit_status = 0;
  563.      return;
  564.       };
  565.       $filename = $relpath if defined $relpath;
  566.    }
  567.  
  568.    undef $heading;
  569.    $tagNum = 0;
  570.  
  571.  READLINE:
  572.    while (<PAGE>)
  573.    {
  574.       $line .= $_;
  575.       # $line =~ s/\n/ /g;
  576.  
  577.       while ($line =~ /</o)
  578.       {
  579.      $tail = $'; #'
  580.      undef $lastNonTag;
  581.      if ($` !~ /^\s*$/o)
  582.      {
  583.         $lastNonTag = $`;
  584.  
  585.         # check for illegal text context
  586.         if (defined $badTextContext{$tags[$#tags]})
  587.         {
  588.            &whine($., 'bad-text-context',$tags[$#tags],
  589.               $badTextContext{$tags[$#tags]});
  590.         }
  591.  
  592.         $lnt = $lastNonTag;
  593.         while ($lnt =~ />/o)
  594.         {
  595.            $nl = $lnt = $';
  596.            $nl =~ s/[^\n]//go;
  597.            if ('PRE' =~ /^($tagRE)$/)
  598.            {
  599.           &whine($. - length($nl), 'meta-in-pre', '>', '>');
  600.            }
  601.            else
  602.            {
  603.           &whine($. - length($nl), 'literal-metacharacter', '>', '>');
  604.            }
  605.         }
  606.      }
  607.  
  608.      #--------------------------------------------------------
  609.      #== SGML comment: <!-- ... blah blah ... -->
  610.      #--------------------------------------------------------
  611.      if ($tail =~ /^!--/o)
  612.      {
  613.  
  614.         $commentline = $. unless defined $commentline;
  615.  
  616.         # push lastNonTag onto word list for spell checking
  617.  
  618.         $ct = $';
  619.         next READLINE unless $ct =~ /--\s*>/o;
  620.  
  621.         undef $commentline;
  622.  
  623.         $comment = $`;
  624.         $line = $';
  625.  
  626.         # markup embedded in comment can confuse some (most? :-) browsers
  627.         &whine($., 'markup-in-comment') if $comment =~ /<\s*[^>]+>/o;
  628.         next;
  629.      }
  630.      undef $commentline;
  631.  
  632.      next READLINE unless $tail =~ /^(\s*)([^>]*)>/o;
  633.  
  634.  
  635.      &whine($., 'leading-whitespace', $2) if $1 ne '';
  636.  
  637.          $line = $';
  638.          ($tag = $2) =~ s/\n/ /go;
  639.      $id = $tag;
  640.  
  641.          &whine($., 'unknown-element', $id),next if $id =~ /^\s*$/o;
  642.  
  643.      # push lastNonTag onto word list for spell checking
  644.  
  645.          undef $tail;
  646.          undef $closing;
  647.  
  648.          #-- <!DOCTYPE ... > is ignored for now.
  649.          $seenTag{'DOCTYPE'}=1,next if $id =~ /^!doctype/io;
  650.  
  651.          if (!$whined{'require-doctype'} && !$seenTag{'DOCTYPE'})
  652.      {
  653.             &whine($., 'require-doctype');
  654.             $whined{'require-doctype'} = 1;
  655.      }
  656.  
  657.      $closing = 0;
  658.          if ($id =~ m@^/@o)
  659.          {
  660.             $id =~ s@^/@@o;
  661.         $ID = "\U$id";
  662.             $closing = 1;
  663.          }
  664.  
  665.      &CheckAttributes();
  666.  
  667.      $TAG = ($closing ? '/' : '').$ID;
  668.      if (defined $mustFollow{$TAG})
  669.      {
  670.         $ok = 0;
  671.         foreach $pre (split(/\|/, $mustFollow{$TAG}))
  672.         {
  673.            ($ok=1),last if $pre eq $lastTAG;
  674.         }
  675.         if (!$ok || $lastNonTag !~ /^\s*$/o)
  676.         {
  677.            &whine($., 'must-follow', $TAG, $mustFollow{$TAG});
  678.         }
  679.      }
  680.  
  681.      #-- catch empty container elements
  682.      if ($closing && $ID eq $lastTAG && $lastNonTag =~ /^\s*$/o
  683.          && $tagNums[$#tagNums] == ($tagNum - 1)
  684.          && $ID ne 'TEXTAREA' && $ID ne 'TD')
  685.      {
  686.         &whine($., 'empty-container', $ID);
  687.      }
  688.  
  689.      #-- special case for empty optional container elements
  690.      if (!$closing && $ID eq $tags[$#tags] && $lastTAG eq $ID
  691.          && $ID =~ /^($maybePaired)$/
  692.          && $tagNums[$#tagNums] == ($tagNum - 1)
  693.          && $lastNonTag =~ /^\s*$/o)
  694.      {
  695.         $t = pop @tags;
  696.         $tline = pop @taglines;
  697.         pop @tagNums;
  698.         &whine($tline, 'empty-container', $ID);
  699.         $tagRE = join('|',@tags);
  700.      }
  701.  
  702.          #-- whine about unrecognized element, and do no more checks ----
  703.          if ($id !~ /^($legalElements)$/io)
  704.      {
  705.         if ($id =~ /^($netscapeElements|$javaElement|$msElement)$/io)
  706.         {
  707.            &whine($., 'extension-markup', ($closing ? "/$id" : "$id"));
  708.         }
  709.         else
  710.         {
  711.            &whine($., 'unknown-element', ($closing ? "/$id" : "$id"));
  712.         }
  713.         next;
  714.      }
  715.  
  716.          if ($closing == 0 && defined $requiredAttributes{$ID})
  717.          {
  718.         @argkeys = keys %args;
  719.         foreach $attr (split(/\|/,$requiredAttributes{$ID}))
  720.         {
  721.            unless (defined $args{$attr})
  722.            {
  723.           &whine($., 'required-attribute', $attr, $id);
  724.            }
  725.         }
  726.          }
  727.          elsif ($closing == 0 && $id =~ /^($expectArgsRE)$/io)
  728.          {
  729.             &whine($., 'expected-attribute', $id) unless defined %args;
  730.          }
  731.  
  732.          #--------------------------------------------------------
  733.          #== check case of tags
  734.          #--------------------------------------------------------
  735.          &whine($., 'upper-case', $id) if $id ne $ID;
  736.          &whine($., 'lower-case', $id) if $id ne "\L$id";
  737.  
  738.  
  739.          #--------------------------------------------------------
  740.          #== if tag id is /foo, then strip slash, and mark as a closer
  741.          #--------------------------------------------------------
  742.          if ($closing)
  743.          {
  744.         if ($ID !~ /^($pairElements)$/o)
  745.         {
  746.            &whine($., 'illegal-closing', $id);
  747.         }
  748.  
  749.             if ($ID eq 'A' && $lastNonTag =~ /^\s*here\s*$/io)
  750.             {
  751.                &whine($., 'here-anchor');
  752.             }
  753.  
  754.         if ($ID eq 'TITLE' && length($lastNonTag) > 64)
  755.         {
  756.            &whine($., 'title-length');
  757.         }
  758.  
  759.         #-- end of HEAD, did we see a TITLE in the HEAD element? ----
  760.         &whine($., 'require-head') if $ID eq 'HEAD' && !$seenTag{'TITLE'};
  761.  
  762.         #-- was there a <LINK REV=MADE HREF="mailto:.."> element in HEAD?
  763.         &whine($., 'mailto-link') if $ID eq 'HEAD' && $seenMailtoLink == 0;
  764.          }
  765.          else
  766.          {
  767.             #--------------------------------------------------------
  768.             # do context checks.  Should really be a state machine.
  769.             #--------------------------------------------------------
  770.  
  771.         if (defined $physicalFontElements{$ID})
  772.         {
  773.            &whine($., 'physical-font', $ID, $physicalFontElements{$ID});
  774.         }
  775.  
  776.         if ($ID =~ /^H[1-6]$/o && 'A' =~ /^($tagRE)$/)
  777.         {
  778.            &whine($., 'heading-in-anchor', $ID);
  779.         }
  780.  
  781.             if ($ID eq 'A' && defined $args{'HREF'})
  782.             {
  783.            $target = $args{'HREF'};
  784.                if ($target =~ /([^:]+):\/\/([^\/]+)(.*)$/o
  785.            || $target =~ /^(news|mailto):/o
  786.            || $target =~ /^\//o)
  787.                {
  788.                }
  789.                else
  790.                {
  791.           $target =~ s/#.*$//o;
  792.           if ($target !~ /^\s*$/o && ! -f $target && ! -d $target)
  793.           {
  794.              &whine($., 'bad-link', $target);
  795.           }
  796.                }
  797.             }
  798.  
  799.             if ($ID =~ /^H(\d)$/o)
  800.         {
  801.                if (defined $heading && $1 - $heading > 1)
  802.                {
  803.               &whine($., 'heading-order', $ID, $heading, $headingLine);
  804.                }
  805.                $heading     = $1;
  806.                $headingLine = $.;
  807.         }
  808.  
  809.         #-- check for mailto: LINK ------------------------------
  810.         if ($ID eq 'LINK' && $args{'REV'} =~ /^made$/io
  811.         && $args{'HREF'} =~ /^mailto:/io)
  812.         {
  813.            $seenMailtoLink = 1;
  814.         }
  815.  
  816.         if (defined $onceOnly{$ID})
  817.         {
  818.            &whine($., 'once-only', $ID, $seenTag{$ID}) if $seenTag{$ID};
  819.         }
  820.             $seenTag{$ID} = $.;
  821.  
  822.             &whine($., 'body-no-head') if $ID eq 'BODY' && !$seenTag{'HEAD'};
  823.  
  824.             if ($ID ne 'HTML' && $ID ne '!DOCTYPE' && !$seenTag{'HTML'}
  825.                 && !$whined{'outer-html'})
  826.             {
  827.                &whine($., 'html-outer');
  828.                $whined{'outer-html'} = 1;
  829.             }
  830.  
  831.         #-- check for illegally nested elements ---------------------
  832.         if ($ID =~ /^($nonNest)$/o && $ID =~ /^($tagRE)$/)
  833.         {
  834.            for ($i=$#tags; $tags[$i] ne $ID; --$i)
  835.            {
  836.            }
  837.            &whine($., 'nested-element', $ID, $taglines[$i]);
  838.         }
  839.  
  840.         &whine($., 'unknown-element', $ID) unless $ID =~ /^($legalElements)$/o;
  841.  
  842.         #--------------------------------------------------------
  843.         # check for tags which have a required context
  844.         #--------------------------------------------------------
  845.         if (defined ($reqCon = $requiredContext{$ID}))
  846.         {
  847.            $ok = 0;
  848.            foreach $context (split(/\|/, $requiredContext{$ID}))
  849.            {
  850.           ($ok=1),last if $context =~ /^($tagRE)$/;
  851.            }
  852.            unless ($ok)
  853.            {
  854.                   &whine($., 'required-context', $ID, $requiredContext{$ID});
  855.            }
  856.         }
  857.  
  858.         #--------------------------------------------------------
  859.         # check for tags which can only appear in the HEAD element
  860.         #--------------------------------------------------------
  861.         if ($ID =~ /^($headTagsRE)$/o && 'HEAD' !~ /^($tagRE)$/)
  862.         {
  863.                &whine($., 'head-element', $ID);
  864.         }
  865.  
  866.         if (! defined $okInHead{$ID} && 'HEAD' =~ /^($tagRE)$/)
  867.         {
  868.                &whine($., 'non-head-element', $ID);
  869.         }
  870.  
  871.         #--------------------------------------------------------
  872.         # check for tags which have been deprecated (now obsolete)
  873.         #--------------------------------------------------------
  874.         &whine($., 'obsolete', $ID) if $ID =~ /^($obsoleteTags)$/o;
  875.          }
  876.  
  877.          #--------------------------------------------------------
  878.          #== was tag of type <TAG> ... </TAG>?
  879.          #== welcome to kludgeville, population seems to be on the increase!
  880.          #--------------------------------------------------------
  881.          if ($ID =~ /^($pairElements)$/o)
  882.          {
  883.         if (!$closing && $ID eq $tags[$#tags] &&
  884.         $ID =~ /^($maybePaired)$/o)
  885.         {
  886.            pop @tags;
  887.            pop @tagNums;
  888.            pop @taglines;
  889.            $tagRE = join('|',@tags);
  890.         }
  891.         if ($closing)
  892.         {
  893.            # trailing whitespace in content of container element
  894.            if ($lastNonTag =~ /\S\s+$/o && $ID =~ /^($cuddleContainers)$/o)
  895.            {
  896.           &whine($., 'container-whitespace', 'trailing', $ID);
  897.            }
  898.  
  899.            #-- if we have a closing tag, and the tag(s) on top of the stack
  900.            #-- are optional closing tag elements, pop tag off the stack,
  901.            #-- unless it matches the current closing tag
  902.            if (@tags > 0 && $tags[$#tags] ne $ID
  903.            && $tags[$#tags] =~ /^($maybePaired)$/o
  904.            && $lastNonTag =~ /^\s*$/o
  905.            && $tagNums[$#tagNums] == ($tagNum - 1)
  906.            && $ID ne 'TD')
  907.            {
  908.           $tline = $taglines[$#taglines];
  909.           &whine($tline, 'empty-container', $tags[$#tags]);
  910.            }
  911.  
  912.            while (@tags > 0 && $tags[$#tags] ne $ID
  913.               && $tags[$#tags] =~ /^($maybePaired)$/o)
  914.            {
  915.           pop @tags;
  916.           pop @tagNums;
  917.           pop @taglines;
  918.            }
  919.            $tagRE = join('|',@tags);
  920.         }
  921.         else
  922.         {
  923.            # leading whitespace in content of container element
  924.            if ($line =~ /^\s+/o && $ID =~ /^($cuddleContainers)$/o)
  925.            {
  926.           &whine($., 'container-whitespace', 'leading', $ID);
  927.            }
  928.         }
  929.  
  930.             if ($closing && $tags[$#tags] eq $ID)
  931.             {
  932.                &PopEndTag();
  933.             }
  934.             elsif ($closing && $tags[$#tags] ne $ID)
  935.             {
  936.            #-- closing tag does not match opening tag on top of stack
  937.            if ($ID =~ /^($tagRE)$/)
  938.            {
  939.           # If we saw </HTML>, </HEAD>, or </BODY>, then we try
  940.           # and resolve anything inbetween on the tag stack
  941.           if ($ID =~ /^(HTML|HEAD|BODY)$/o)
  942.           {
  943.              while ($tags[$#tags] ne $ID)
  944.              {
  945.             $ttag = pop @tags;
  946.             pop @tagNums;
  947.             $ttagline = pop @taglines;
  948.             if ($ttag !~ /^($maybePaired)$/o)
  949.             {
  950.                &whine($., 'unclosed-element', $ttag, $ttagline);
  951.             }
  952.  
  953.             #-- does top of stack match top of orphans stack? --
  954.             while (@orphans > 0 && @tags > 0
  955.                    && $orphans[$#orphans] eq $tags[$#tags])
  956.             {
  957.                pop @orphans;
  958.                pop @orphanlines;
  959.                pop @tags;
  960.                pop @tagNums;
  961.                pop @taglines;
  962.             }
  963.              }
  964.  
  965.              #-- pop off the HTML, HEAD, or BODY tag ------------
  966.              pop @tags;
  967.              pop @tagNums;
  968.              pop @taglines;
  969.              $tagRE = join('|',@tags);
  970.           }
  971.           else
  972.           {
  973.              #-- matched opening tag lower down on stack
  974.              push(@orphans, $ID);
  975.              push(@orphanlines, $.);
  976.           }
  977.            }
  978.            else
  979.            {
  980.                   if ($ID =~ /^H[1-6]$/o && $tags[$#tags] =~ /^H[1-6]$/o)
  981.                   {
  982.              &whine($., 'heading-mismatch', $tags[$#tags], $ID);
  983.                      &PopEndTag();
  984.                   }
  985.           else
  986.           {
  987.              &whine($., 'mis-match', $ID);
  988.                   }
  989.            }
  990.             }
  991.             else
  992.             {
  993.                push(@tags,$ID);
  994.                $tagRE = join('|',@tags);
  995.                push(@tagNums,$tagNum);
  996.                push(@taglines,$.);
  997.             }
  998.          }
  999.  
  1000.          #--------------------------------------------------------
  1001.          #== inline images (IMG) should have an ALT argument :-)
  1002.          #--------------------------------------------------------
  1003.          &whine($., 'img-alt') if ($ID eq 'IMG'
  1004.                    && !defined $args{'ALT'}
  1005.                    && !$closing);
  1006.  
  1007.          #--------------------------------------------------------
  1008.          #== WIDTH & HEIGHT on inline images (IMG) can help browsers
  1009.          #--------------------------------------------------------
  1010.          &whine($., 'img-size') if ($ID eq 'IMG'
  1011.                    && !defined $args{'WIDTH'}
  1012.                    && !defined $args{'HEIGHT'}
  1013.                    && !$closing);
  1014.  
  1015.       } continue {
  1016.      $lastTagNum = $tagNum;
  1017.      ++$tagNum;
  1018.          $lastTAG = $TAG;
  1019.       }
  1020.       $lastNonTag = $line;
  1021.    }
  1022.    close PAGE;
  1023.  
  1024.    if (defined $commentline)
  1025.    {
  1026.       &whine($commentline, 'unclosed-comment');
  1027.       return;
  1028.    }
  1029.  
  1030.    while (@tags > 0)
  1031.    {
  1032.       $tag = shift(@tags);
  1033.       shift(@tagNums);
  1034.       $line = shift(@taglines);
  1035.       if ($tag !~ /^($maybePaired)$/o)
  1036.       {
  1037.      &whine($., 'unclosed-element', $tag, $line);
  1038.       }
  1039.    }
  1040.  
  1041.    for (@expectedTags)
  1042.    {
  1043.       # if we haven't seen TITLE but have seen HEAD
  1044.       # then we'll have already whined about the lack of a TITLE element
  1045.       next if $_ eq 'TITLE' && !$seenTag{$_} && $seenTag{'HEAD'};
  1046.       next if $_ eq 'BODY' && $seenTag{'FRAMESET'};
  1047.       push(@notSeen,$_) unless $seenTag{$_};
  1048.    }
  1049.    if (@notSeen > 0)
  1050.    {
  1051.       printf ("%sexpected tag(s) not seen: @notSeen\n",
  1052.               ($opt_s ? "" : "$filename(-): "));
  1053.       $exit_status = 1;
  1054.    }
  1055. }
  1056.  
  1057. #========================================================================
  1058. # Function:    CheckAttributes
  1059. # Purpose:    If the tag has attributes, check them for validity.
  1060. #========================================================================
  1061. sub CheckAttributes
  1062. {
  1063.    undef %args;
  1064.  
  1065.    if ($closing == 0 && $tag =~ m|^(\S+)\s+(.*)|)
  1066.    {
  1067.       ($id,$tail) = ($1,$2);
  1068.       $ID = "\U$id";
  1069.  
  1070.       # don't worry, or warn, about attributes of unknown elements
  1071.       return unless $ID =~ /^($legalElements)$/io;
  1072.  
  1073.       $tail =~ s/\n/ /go;
  1074.  
  1075.       # check for odd number of quote characters
  1076.       ($quotes = $tail) =~ s/[^""]//go;
  1077.       &whine($., 'odd-quotes', $tag) if length($quotes) % 2 == 1;
  1078.  
  1079.       $novalue = 0;
  1080.       $valid = $validAttributes{$ID};
  1081.       while ($tail =~ /^\s*([^=\s]+)\s*=\s*(.*)$/o
  1082.          # catch attributes like ISMAP for IMG, with no arg
  1083.          || ($tail =~ /^\s*(\S+)(.*)/o && ($novalue = 1)))
  1084.       {
  1085.      $arg = "\U$1";
  1086.      $rest = $2;
  1087.  
  1088.      &whine($., 'unexpected-open', $tag) if $arg =~ /</o;
  1089.  
  1090.      if ($arg !~ /^($valid)$/i && $ID =~ /^($legalElements)$/o)
  1091.      {
  1092.         if ($arg =~ /^($netscapeAttributes{$ID})$/io
  1093.                 || $arg =~ /^($msAttributes{$ID})$/io)
  1094.         {
  1095.            &whine($., 'extension-attribute', $arg, $id);
  1096.         }
  1097.         else
  1098.         {
  1099.            &whine($., 'unknown-attribute', $id, $arg);
  1100.         }
  1101.      }
  1102.  
  1103.      #-- catch repeated attributes.  for example:
  1104.      #--     <IMG SRC="foo.gif" SRC="bar.gif">
  1105.      if (defined $args{$arg})
  1106.      {
  1107.         &whine($., 'repeated-attribute', $arg, $id);
  1108.      }
  1109.  
  1110.      if ($novalue)
  1111.      {
  1112.         $args{$arg} = '';
  1113.         $tail = $rest;
  1114.      }
  1115.      elsif ($rest =~ /^'([^'']+)'(.*)$/o)
  1116.          {
  1117.         &whine($., 'attribute-delimiter', $arg, $ID);
  1118.             $args{$arg} = $1;
  1119.             $tail = $2;
  1120.          }
  1121.      elsif ($rest =~ /^"([^""]*)"(.*)$/o
  1122.         || $rest =~ /^'([^'']*)'(.*)$/o)
  1123.          {
  1124.             $args{$arg} = $1;
  1125.             $tail = $2;
  1126.          }
  1127.      elsif ($rest =~ /^(\S+)(.*)$/o)
  1128.          {
  1129.             $attrValue = $1;
  1130.             $args{$arg} = $attrValue;
  1131.             $tail = $2;
  1132.             if ($attrValue =~ /[^-.A-Za-z0-9]/o)
  1133.             {
  1134.                &whine($., 'quote-attribute-value', $arg, $attrValue, $ID);
  1135.             }
  1136.          }
  1137.          else
  1138.          {
  1139.         $args{$arg} = $rest;
  1140.         $tail = '';
  1141.          }
  1142.      $novalue = 0;
  1143.       }
  1144.       foreach $attr (keys %args)
  1145.       {
  1146.          if (defined $attributeFormat{$attr} &&
  1147.              $args{$attr} !~ /^($attributeFormat{$attr})$/i)
  1148.          {
  1149.             &whine($., 'attribute-format', $attr, $id, $args{$attr});
  1150.          }
  1151.       }
  1152.       &whine($., 'unexpected-open', $tag) if $tail =~ /</o;
  1153.    }
  1154.    else
  1155.    {
  1156.       if ($closing && $id =~ m|^(\S+)\s+(.*)|)
  1157.       {
  1158.      &whine($., 'closing-attribute', $tag);
  1159.      $id = $1;
  1160.       }
  1161.       $ID = "\U$id";
  1162.    }
  1163. }
  1164.  
  1165. #========================================================================
  1166. # Function:    whine
  1167. # Purpose:    Give a standard format whine:
  1168. #            filename(line #): <message>
  1169. #               The associative array `enabled' is used as a gating
  1170. #               function, to suppress or enable each warning.  Every
  1171. #               warning has an associated identifier, which is used to
  1172. #               refer to the warning, and as the index into the hash.
  1173. #========================================================================
  1174. sub whine
  1175. {
  1176.    local($line, $id, @argv) = @_;
  1177.    local($mstyle)        = $variable{'message-style'};
  1178.  
  1179.  
  1180.    return unless $enabled{$id};
  1181.    $exit_status = 1;
  1182.    (print "$filename:$line:$id\n"), return             if $mstyle eq 'terse';
  1183.    (eval "print \"\$filename($line): $message{$id}\n\""), return if $mstyle eq 'lint';
  1184.    (eval "print \"line $line: $message{$id}\n\""), return if $mstyle eq 'short';
  1185.  
  1186.    die "Unknown message style `$mstyle'\n";
  1187. }
  1188.  
  1189. #========================================================================
  1190. # Function:    ReadConfigFile
  1191. # Purpose:    Read the specified configuration file. This is used to
  1192. #        the user's .weblintrc file, or the global system config
  1193. #        file, if the user doesn't have one.
  1194. #========================================================================
  1195. sub ReadConfigFile
  1196. {
  1197.    local($filename) = @_;
  1198.    local(*CONFIG);
  1199.    local($arglist);
  1200.    local($keyword, $value);
  1201.    local($_);
  1202.  
  1203.  
  1204.    open(CONFIG,"< $filename") || do
  1205.    {
  1206.       print WARNING "Unable to read config file `$filename': $!\n";
  1207.       return;
  1208.    };
  1209.  
  1210.    while (<CONFIG>)
  1211.    {
  1212.       chop;
  1213.       s/#.*$//;
  1214.       next if /^\s*$/o;
  1215.  
  1216.       #-- match keyword: process one or more argument -------------------
  1217.       if (/^\s*(enable|disable|extension|ignore)\s+(.*)$/io)
  1218.       {
  1219.      $keyword = "\U$1";
  1220.      $arglist = $2;
  1221.      while ($arglist =~ /^\s*(\S+)/o)
  1222.      {
  1223.         $value = "\L$1";
  1224.  
  1225.         &enableWarning($1, 1) if $keyword eq 'ENABLE';
  1226.  
  1227.         &enableWarning($1, 0) if $keyword eq 'DISABLE';
  1228.  
  1229.         $ignore{"\U$1"} = 1 if $keyword eq 'IGNORE';
  1230.  
  1231.         &AddExtension("\L$1") if $keyword eq 'EXTENSION';
  1232.  
  1233.         $arglist = $';
  1234.      }
  1235.       }
  1236.       elsif (/^\s*set\s+(\S+)\s*=\s*(.*)/o)
  1237.       {
  1238.          # setting a weblint variable
  1239.          if (defined $variable{$1})
  1240.          {
  1241.             $variable{$1} = $2;
  1242.          }
  1243.          else
  1244.          {
  1245.             print WARNING "Unknown variable `$1' in configuration file\n";
  1246.          }
  1247.       }
  1248.       elsif (/^\s*use\s*global\s*weblintrc/o)
  1249.       {
  1250.      if (-f $SITE_RCFILE)
  1251.      {
  1252.         &ReadConfigFile($SITE_RCFILE);
  1253.      }
  1254.      else
  1255.      {
  1256.         print WARNING "$PROGRAM: unable to read global config file\n";
  1257.         next;
  1258.      }
  1259.       }
  1260.       else
  1261.       {
  1262.      print WARNING ("$PROGRAM: ignoring unknown sequence (\"$_\") ".
  1263.                        "in config file $filename\n");
  1264.       }
  1265.    }
  1266.  
  1267.    close CONFIG;
  1268. }
  1269.  
  1270. #========================================================================
  1271. # Function:    enableWarning
  1272. # Purpose:    Takes a warning identifier and an integer (boolean)
  1273. #        flag which specifies whether the warning should be
  1274. #        enabled.
  1275. #========================================================================
  1276. sub enableWarning
  1277. {
  1278.    local($id, $enabled) = @_;
  1279.  
  1280.  
  1281.    if (! defined $enabled{$id})
  1282.    {
  1283.       print WARNING "$PROGRAM: unknown warning identifier \"$id\"\n";
  1284.       return 0;
  1285.    }
  1286.  
  1287.    $enabled{$id} = $enabled;
  1288.  
  1289.    #
  1290.    # ensure consistency: if you just enabled upper-case,
  1291.    # then we should make sure that lower-case is disabled
  1292.    #
  1293.    $enabled{'lower-case'} = 0 if $_ eq 'upper-case';
  1294.    $enabled{'upper-case'} = 0 if $_ eq 'lower-case';
  1295.    $enabled{'upper-case'} = $enabled{'lower-case'} = 0 if $_ eq 'mixed-case';
  1296.  
  1297.    return 1;
  1298. }
  1299.  
  1300. #========================================================================
  1301. # Function:    AddExtension
  1302. # Purpose:    Extend the HTML understood.  Currently supported extensions:
  1303. #            Netscape  - the netscape extensions proposed by
  1304. #                                   Netscape Communications, Inc.  See:
  1305. #            Java      - Java elements
  1306. #            Microsoft - the extensions for Microsoft Internet
  1307. #                    Explorer
  1308. #               http://www.netscape.com/home/services_docs/html-extensions.html
  1309. #========================================================================
  1310. sub AddExtension
  1311. {
  1312.    local($extension) = @_;
  1313.    local(@extlist);
  1314.    local($element);
  1315.  
  1316.    if ($extension =~ /,/o)
  1317.    {
  1318.       @extlist = split(/\s*,\s*/, $extension);
  1319.       &AddExtension(shift @extlist) while @extlist > 0;
  1320.       return;
  1321.    }
  1322.  
  1323.    if (   $extension ne 'netscape'
  1324.        && $extension ne 'java'
  1325.        && $extension ne 'microsoft')
  1326.    {
  1327.       warn "$PROGRAM: unknown extension `$extension' -- ignoring.\n";
  1328.       return;
  1329.    }
  1330.  
  1331.    #---------------------------------------------------------------------
  1332.    # Java extensions
  1333.    #---------------------------------------------------------------------
  1334.  
  1335.    if ($extension eq 'java')
  1336.    {
  1337.       $legalElements .= '|'.$javaElements;
  1338.       $pairElements  .= '|APPLET';
  1339.  
  1340.       &AddAttributes('APPLET', 'CODEBASE', 'CODE', 'ALT', 'NAME',
  1341.                    'WIDTH', 'HEIGHT', 'ALIGN', 'VSPACE', 'HSPACE');
  1342.       &AddAttributes('PARAM', 'NAME', 'VALUE');
  1343.  
  1344.       $requiredContext{'PARAM'} = 'APPLET';
  1345.       $requiredAttributes{'APPLET'} = 'CODE|WIDTH|HEIGHT';
  1346.       $requiredAttributes{'PARAM'} = 'NAME|VALUE';
  1347.  
  1348.       return;
  1349.    }
  1350.  
  1351.    #---------------------------------------------------------------------
  1352.    # Microsoft extensions
  1353.    #---------------------------------------------------------------------
  1354.    if ($extension eq 'microsoft')
  1355.    {      
  1356.       #-- new element attributes for existing elements ---------------------
  1357.       foreach $element (keys %msAttributes)
  1358.       {
  1359.      &AddAttributes($element, split(/\|/, $msAttributes{$element}));
  1360.       }
  1361.  
  1362.       $legalElements .= '|'.$msElements;
  1363.       $pairElements  .= '|CENTER|FONT|MAP|MARQUEE|NOBR';
  1364.       $expectArgsRE .= '|FONT';
  1365.  
  1366.       $attributeFormat{'LOOP'} = '\d+|INFINITE';
  1367.       $okInHead{'BGSOUND'} = 1;
  1368.       $requiredAttributes{'BGSOUND'} = 'SRC';
  1369.       $attributeFormat{'LEFTMARGIN'} = '\d+';
  1370.       $attributeFormat{'TOPMARGIN'} = '\d+';
  1371.  
  1372.       $requiredContext{'AREA'}  = 'MAP';
  1373.       $requiredAttributes{'MAP'}   = 'NAME';
  1374.       $requiredAttributes{'AREA'}  = 'COORDS';
  1375.  
  1376.       #-- MARQUEE attributes
  1377.       $attributeFormat{'BEHAVIOR'} = 'SCROLL|SLIDE|ALTERNATE';
  1378.       $attributeFormat{'DIRECTION'} = 'LEFT|RIGHT';
  1379.       $attributeFormat{'WIDTH'} = '\d+%?';
  1380.       $attributeFormat{'HEIGHT'} = '\d+%?';
  1381.  
  1382.       # attribute format check for attributes which take colors
  1383.       $attributeFormat{'ALINK'} = $attributeFormat{'VLINK'} =
  1384.       $attributeFormat{'LINK'} = $attributeFormat{'TEXT'} =
  1385.       $attributeFormat{'BGCOLOR'} =
  1386.       $attributeFormat{'COLOR'} = $attributeFormat{'BORDERCOLOR'} =
  1387.       $attributeFormat{'BORDERCOLORLIGHT'} =
  1388.       $attributeFormat{'BORDERCOLORDARK'} = $colorRE.'|'.$msColors;
  1389.  
  1390.    }
  1391.  
  1392.    #---------------------------------------------------------------------
  1393.    # Netscape extensions
  1394.    #---------------------------------------------------------------------
  1395.  
  1396.    if ($extension eq 'netscape')
  1397.    {
  1398.       #-- new element attributes for existing elements ---------------------
  1399.       foreach $element (keys %netscapeAttributes)
  1400.       {
  1401.      &AddAttributes($element, split(/\|/, $netscapeAttributes{$element}));
  1402.       }
  1403.  
  1404.       #-- formats for new attributes ---------------------------------------
  1405.  
  1406.       $attributeFormat{'SIZE'} = '[-+]?\d+';
  1407.       $attributeFormat{'MARGINWIDTH'} = '\d+';
  1408.       $attributeFormat{'MARGINHEIGHT'} = '\d+';
  1409.       $attributeFormat{'SCROLLING'} = 'NO|YES|AUTO';
  1410.       $attributeFormat{'WIDTH'} = '\d+%?';
  1411.       $attributeFormat{'TYPE'} .= '|[AaIi1]|disc|square|circle';
  1412.  
  1413.       #-- new elements -----------------------------------------------------
  1414.  
  1415.       $legalElements .= '|'.$netscapeElements;
  1416.       $pairElements  .= '|BLINK|CENTER|FONT|FRAMESET|NOFRAMES|NOBR|MAP|SCRIPT';
  1417.       $requiredContext{'AREA'}  = 'MAP';
  1418.       $requiredContext{'FRAME'} = 'FRAMESET';
  1419.       $requiredAttributes{'MAP'}   = 'NAME';
  1420.       $requiredAttributes{'AREA'}  = 'COORDS';
  1421.  
  1422.       # this should really be specific to ROWS and COLS in FRAMESET element<
  1423.       $attributeFormat{'ROWS'} =
  1424.      $attributeFormat{'COLS'} = '\d+|(\d*[*%]?,)*\d*[*%]?';
  1425.  
  1426.       # this is for TEXTAREA
  1427.       $attributeFormat{'WRAP'} .= '|SOFT|HARD';
  1428.  
  1429.       # attribute format check for attributes which take colors
  1430.       $attributeFormat{'ALINK'} = $attributeFormat{'VLINK'} =
  1431.       $attributeFormat{'LINK'} = $attributeFormat{'TEXT'} =
  1432.       $attributeFormat{'BGCOLOR'} = $colorRE;
  1433.       $attributeFormat{'ALIGN'} .= '|TEXTTOP|ABSMIDDLE|BASELINE|ABSBOTTOM';
  1434.  
  1435.       # BASE can take just a TARGET attribute, HREF not required therefore
  1436.       delete $requiredAttributes{'BASE'};
  1437.  
  1438.       $expectArgsRE .= '|FONT|BASE';
  1439.  
  1440.       $okInHead{'SCRIPT'} = 1;
  1441.    }
  1442. }
  1443.  
  1444. sub AddAttributes
  1445. {
  1446.    local($element,@attributes) = @_;
  1447.    local($attr);
  1448.  
  1449.  
  1450.    $attr = join('|', @attributes);
  1451.    if (defined $validAttributes{$element})
  1452.    {
  1453.       $validAttributes{$element} .= "|$attr";
  1454.    }
  1455.    else
  1456.    {
  1457.       $validAttributes{$element} = "$attr";
  1458.    }
  1459. }
  1460.  
  1461. #========================================================================
  1462. # Function:    ListWarnings()
  1463. # Purpose:    List all supported warnings, with identifier, and
  1464. #        whether the warning is enabled.
  1465. #========================================================================
  1466. sub ListWarnings
  1467. {
  1468.    local($id);
  1469.    local($message);
  1470.  
  1471.  
  1472.    foreach $id (sort keys %enabled)
  1473.    {
  1474.       ($message = $message{$id}) =~ s/\$argv\[\d+\]/.../g;
  1475.       $message =~ s/\\"/"/g;
  1476.       print WARNING "$id (", ($enabled{$id} ? "enabled" : "disabled"), ")\n";
  1477.       print WARNING "    $message\n\n";
  1478.    }
  1479. }
  1480.  
  1481. sub CheckURL
  1482. {
  1483.    local($url)        = @_;
  1484.    local($workfile)    = "$TMPDIR/$PROGRAM.$$";
  1485.    local($urlget)    = $variable{'url-get'};
  1486.  
  1487.  
  1488.    die "$PRORGAM: url-get variable is not defined -- ".
  1489.        "don't know how to get $url\n" unless defined $urlget;
  1490.  
  1491.    system("$urlget $url > $workfile");
  1492.    &WebLint($workfile, $url);
  1493.    unlink $workfile;
  1494. }
  1495.  
  1496. sub PrintToDo
  1497. {
  1498.    print STDERR "$todo";
  1499.    exit 0;
  1500. }
  1501.  
  1502. #========================================================================
  1503. # Function:    wanted
  1504. # Purpose:    This is called by &find() to determine whether a file
  1505. #               is wanted.  We're looking for files, with the filename
  1506. #               extension .html or .htm.
  1507. #========================================================================
  1508. sub wanted
  1509. {
  1510.    local($foundIndex);
  1511.  
  1512.    if (-d $_)
  1513.    {
  1514.       $foundIndex = 0;
  1515.       foreach $legalIndex (@dirIndices)
  1516.       {
  1517.          $foundIndex=1,last if -f "$_/$legalIndex";
  1518.       }
  1519.       if (! $foundIndex)
  1520.       {
  1521.          &whine("$arg/$_", 'directory-index', "@dirIndices");
  1522.       }
  1523.    }
  1524.  
  1525.    /\.($fileExtensions)$/ &&       # valid filename extensions: .html .htm
  1526.       -f $_ &&            # only looking for files
  1527.       (!$opt_l || !-l $_) &&    # ignore symlinks if -l given
  1528.       &WebLint($_,$name);    # check the file
  1529. }
  1530.  
  1531. sub PopEndTag
  1532. {
  1533.    $matched     = pop @tags;
  1534.    pop @tagNums;
  1535.    $matchedLine = pop @taglines;
  1536.  
  1537.    #-- does top of stack match top of orphans stack? --------
  1538.    while (@orphans > 0 && @tags > 0
  1539.       && $orphans[$#orphans] eq $tags[$#tags])
  1540.    {
  1541.       &whine($., 'element-overlap', $orphans[$#orphans],
  1542.          $orphanlines[$#orphanlines], $matched, $matchedLine);
  1543.       pop @orphans;
  1544.       pop @orphanlines;
  1545.       pop @tags;
  1546.       pop @tagNums;
  1547.       pop @taglines;
  1548.    }
  1549.    $tagRE = join('|',@tags);
  1550. }
  1551.  
  1552. #========================================================================
  1553. # Function:    PickTmpdir
  1554. # Purpose:    Pick a temporary working directory. If TMPDIR environment
  1555. #        variable is set, then we try that first.
  1556. #========================================================================
  1557. sub PickTmpdir
  1558. {
  1559.    local(@options) = @_;
  1560.    local($tmpdir);
  1561.  
  1562.    @options = ($ENV{'TMPDIR'}, @options) if defined $ENV{'TMPDIR'};
  1563.    foreach $tmpdir (@options)
  1564.    {
  1565.       return $tmpdir if -d $tmpdir && -w $tmpdir;
  1566.    }
  1567.    die "$PROGRAM: unable to find a temporary directory.\n",
  1568.        ' ' x (length($PROGRAM)+2), "tried: ",join(' ',@options),"\n";
  1569. }
  1570.  
  1571. #========================================================================
  1572. # Function:    ReadDefaults
  1573. # Purpose:    Read the built-in defaults.  These are stored at the end
  1574. #               of the script, after the __END__, and read from the
  1575. #               DATA filehandle.
  1576. #========================================================================
  1577. sub ReadDefaults
  1578. {
  1579.    local(@elements);
  1580.  
  1581.  
  1582.    while (<DATA>)
  1583.    {
  1584.       chop;
  1585.       s/^\s*//;
  1586.       next if /^$/;
  1587.  
  1588.       push(@elements, $_);
  1589.  
  1590.       next unless @elements == 3;
  1591.  
  1592.       ($id, $default, $message) = @elements;
  1593.       $enabled{$id} = ($default eq 'ENABLE');
  1594.       ($message{$id} = $message) =~ s/"/\\"/g;
  1595.       undef @elements;
  1596.    }
  1597. }
  1598.  
  1599. __END__
  1600. upper-case
  1601.     DISABLE
  1602.     tag <$argv[0]> is not in upper case.
  1603. lower-case
  1604.     DISABLE
  1605.     tag <$argv[0]> is not in lower case.
  1606. mixed-case
  1607.     ENABLE
  1608.     tag case is ignored
  1609. here-anchor
  1610.     ENABLE
  1611.     bad form to use `here' as an anchor!
  1612. require-head
  1613.     ENABLE
  1614.     no <TITLE> in HEAD element.
  1615. once-only
  1616.     ENABLE
  1617.     tag <$argv[0]> should only appear once.  I saw one on line $argv[1]!
  1618. body-no-head
  1619.     ENABLE
  1620.     <BODY> but no <HEAD>.
  1621. html-outer
  1622.     ENABLE
  1623.     outer tags should be <HTML> .. </HTML>.
  1624. head-element
  1625.     ENABLE
  1626.     <$argv[0]> can only appear in the HEAD element.
  1627. non-head-element
  1628.     ENABLE
  1629.     <$argv[0]> cannot appear in the HEAD element.
  1630. obsolete
  1631.     ENABLE
  1632.     <$argv[0]> is obsolete.
  1633. mis-match
  1634.     ENABLE
  1635.     unmatched </$argv[0]> (no matching <$argv[0]> seen).
  1636. img-alt
  1637.     ENABLE
  1638.     IMG does not have ALT text defined.
  1639. nested-element
  1640.     ENABLE
  1641.     <$argv[0]> cannot be nested -- </$argv[0]> not yet seen for <$argv[0]> on line $argv[1].
  1642. mailto-link
  1643.     DISABLE
  1644.     did not see <LINK REV=MADE HREF="mailto..."> in HEAD.
  1645. element-overlap
  1646.     ENABLE
  1647.     </$argv[0]> on line $argv[1] seems to overlap <$argv[2]>, opened on line $argv[3].
  1648. unclosed-element
  1649.     ENABLE
  1650.     no closing </$argv[0]> seen for <$argv[0]> on line $argv[1].
  1651. markup-in-comment
  1652.     ENABLE
  1653.     markup embedded in a comment can confuse some browsers.
  1654. unknown-attribute
  1655.     ENABLE
  1656.     unknown attribute "$argv[1]" for element <$argv[0]>.
  1657. leading-whitespace
  1658.     ENABLE
  1659.     should not have whitespace between "<" and "$argv[0]>".
  1660. required-attribute
  1661.     ENABLE
  1662.     the $argv[0] attribute is required for the <$argv[1]> element.
  1663. unknown-element
  1664.     ENABLE
  1665.     unknown element <$argv[0]>.
  1666. odd-quotes
  1667.     ENABLE
  1668.     odd number of quotes in element <$argv[0]>.
  1669. heading-order
  1670.     ENABLE
  1671.     bad style - heading <$argv[0]> follows <H$argv[1]> on line $argv[2].
  1672. bad-link
  1673.     DISABLE
  1674.     target for anchor "$argv[0]" not found.
  1675. expected-attribute
  1676.     ENABLE
  1677.     expected an attribute for <$argv[0]>.
  1678. unexpected-open
  1679.     ENABLE
  1680.     unexpected < in <$argv[0]> -- potentially unclosed element.
  1681. required-context
  1682.     ENABLE
  1683.     illegal context for <$argv[0]> - must appear in <$argv[1]> element.
  1684. unclosed-comment
  1685.     ENABLE
  1686.     unclosed comment (comment should be: <!-- ... -->).
  1687. illegal-closing
  1688.     ENABLE
  1689.     element <$argv[0]> is not a container -- </$argv[0]> not legal.
  1690. extension-markup
  1691.     ENABLE
  1692.     <$argv[0]> is extended markup (use "-x <extension>" to allow this).
  1693. extension-attribute
  1694.     ENABLE
  1695.     attribute `$argv[0]' for <$argv[1]> is extended markup (use "-x <extension>" to allow this).
  1696. physical-font
  1697.     DISABLE
  1698.     <$argv[0]> is physical font markup -- use logical (such as $argv[1]).
  1699. repeated-attribute
  1700.     ENABLE
  1701.     attribute $argv[0] is repeated in element <$argv[1]>
  1702. must-follow
  1703.     ENABLE
  1704.     <$argv[0]> must immediately follow <$argv[1]>
  1705. empty-container
  1706.     ENABLE
  1707.     empty container element <$argv[0]>.
  1708. directory-index
  1709.     ENABLE
  1710.     directory does not have an index file ($argv[0])
  1711. closing-attribute
  1712.     ENABLE
  1713.     closing tag <$argv[0]> should not have any attributes specified.
  1714. attribute-delimiter
  1715.     ENABLE
  1716.     use of ' for attribute value delimiter is not supported by all browsers (attribute $argv[0] of tag $argv[1])
  1717. img-size
  1718.     DISABLE
  1719.     setting WIDTH and HEIGHT attributes on IMG tag can improve rendering performance on some browsers.
  1720. container-whitespace
  1721.     DISABLE
  1722.     $argv[0] whitespace in content of container element $argv[1]
  1723. require-doctype
  1724.     DISABLE
  1725.     first element was not DOCTYPE specification
  1726. literal-metacharacter
  1727.     ENABLE
  1728.     metacharacter '$argv[0]' should be represented as '$argv[1]'
  1729. heading-mismatch
  1730.     ENABLE
  1731.     malformed heading - open tag is <$argv[0]>, but closing is </$argv[1]>
  1732. bad-text-context
  1733.     ENABLE
  1734.     illegal context, <$argv[0]>, for text; should be in $argv[1].
  1735. attribute-format
  1736.     ENABLE
  1737.     illegal value for $argv[0] attribute of $argv[1] ($argv[2])
  1738. quote-attribute-value
  1739.     ENABLE
  1740.     value for attribute $argv[0] ($argv[1]) of element $argv[2] should be quoted (i.e. $argv[0]="$argv[1]")
  1741. meta-in-pre
  1742.     ENABLE
  1743.     you should use '$argv[0]' in place of '$argv[1]', even in a PRE element.
  1744. heading-in-anchor
  1745.     ENABLE
  1746.     <A> should be inside <$argv[0]>, not <$argv[0]> inside <A>.
  1747. title-length
  1748.     ENABLE
  1749.     The HTML spec. recommends the TITLE be no longer than 64 characters.
  1750.